package com.yeskery.nut.script.parser;

import com.yeskery.nut.core.MediaType;
import com.yeskery.nut.core.Method;
import com.yeskery.nut.core.NameAndValue;
import com.yeskery.nut.core.Path;
import com.yeskery.nut.script.Metadata;
import com.yeskery.nut.util.StringUtils;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 文本脚本元数据转换器
 * @author sprout
 * 2022-05-14 10:04
 */
public class TextScriptMetadataParser implements ScriptMetadataParser {

    /** 匹配所有字符的通配符 */
    private static final String ANY_CHAR_WILDCARD = "*";

    /** 键值对分隔符 */
    public static final String KEY_VALUE_SEPARATOR = ",";

    /** 多行分隔符 */
    public static final String MULTI_LINE_SYMBOL = "```";

    /** 换行符 */
    public static final char NEW_LINE_SEPARATOR = '\n';

    @Override
    public Metadata parseMetadata(String script) {
        Metadata metadata = new Metadata();
        metadata.setMediaType(MediaType.TEXT_PLAIN);
        int multiLineSymbolTimes = 0;
        StringBuilder multiBodyBuilder = new StringBuilder();
        for (String lineStr : script.split(String.valueOf(NEW_LINE_SEPARATOR))) {
            String line = lineStr.trim();
            if (line.isEmpty()
                    || line.charAt(0) == ScriptMetadataReader.SCRIPT_START_SYMBOL) {
                continue;
            }
            if (line.charAt(0) == ScriptMetadataReader.SCRIPT_END_SYMBOL) {
                metadata.setBody(multiBodyBuilder.toString());
                break;
            }
            if (multiLineSymbolTimes > 0) {
                if (isMultiBodyLines(line)) {
                    multiBodyBuilder.append(line.replace(MULTI_LINE_SYMBOL, "")).append(NEW_LINE_SEPARATOR);
                } else {
                    multiBodyBuilder.append(line).append(NEW_LINE_SEPARATOR);
                }
                continue;
            }
            int index = line.indexOf(SEPARATOR);
            if (index == -1) {
                throw new ScriptParseException("Illegal Script Expression: [" + line + "]");
            }

            String name = line.substring(0, index).trim();
            String value = index >= line.length() - 1 ? null : line.substring(index + 1).trim();

            if (name.equalsIgnoreCase(Metadata.FIELD_PATH)) {
                // 处理请求路径
                metadata.setPaths(getSetValueBySeparator(value, KEY_VALUE_SEPARATOR, Path::new));
            } else if (name.equalsIgnoreCase(Metadata.FIELD_METHOD)) {
                // 处理请求方法
                Set<Method> methods= getSetValueBySeparator(value, KEY_VALUE_SEPARATOR, v -> {
                    try {
                        if (ANY_CHAR_WILDCARD.equals(v)) {
                            return Method.ALL;
                        }
                        return Method.valueOf(v.toUpperCase());
                    } catch (IllegalArgumentException e) {
                        throw new ScriptParseException("Unsupported Script Method: [" + v + "] At Expression: [" + line + "]");
                    }
                });
                if (methods.contains(Method.ALL) && methods.size() > 1) {
                    metadata.setMethods(Collections.singleton(Method.ALL));
                } else {
                    metadata.setMethods(methods);
                }
            } else if (name.equalsIgnoreCase(Metadata.FIELD_RESPONSE_HEADER)) {
                // 处理响应头
                if (!StringUtils.isEmpty(value)) {
                    metadata.setResponseHeaders(getMultiNameAndValue(value, line, Metadata.FIELD_RESPONSE_HEADER));
                }
            } else if (name.equalsIgnoreCase(Metadata.FIELD_RESPONSE_COOKIE)) {
                // 处理响应cookie
                if (!StringUtils.isEmpty(value)) {
                    metadata.setResponseCookies(getMultiNameAndValue(value, line, Metadata.FIELD_RESPONSE_COOKIE));
                }
            } else if (name.equalsIgnoreCase(Metadata.FIELD_BODY)) {
                // 处理响应体，包括多行内容
                if (!StringUtils.isEmpty(value)) {
                    if (isMultiBodyLines(value)) {
                        multiBodyBuilder.append(getMultiFirstLine(value));
                        multiLineSymbolTimes++;
                    } else {
                        metadata.setBody(value);
                        break;
                    }
                }
            } else {
                throw new ScriptParseException("Unsupported Script Field: [" + name + "] At Expression: [" + line + "]");
            }
        }
        return metadata.getValidMetadata();
    }

    /**
     * 判断是否是多行响应体
     * @param value 响应体
     * @return 是否是多行响应体
     */
    protected boolean isMultiBodyLines(String value) {
        return value.contains(MULTI_LINE_SYMBOL);
    }

    /**
     * 获取多行响应体的第一行内容
     * @param value 响应体
     * @return 多行响应体的第一行内容
     */
    protected String getMultiFirstLine(String value) {
        return value.length() > MULTI_LINE_SYMBOL.length() ? value.substring(MULTI_LINE_SYMBOL.length()) : "";
    }

    /**
     * 通过分隔符获取字符串中的set集合
     * @param value 字符串
     * @param separator 分隔符
     * @return 字符串中分割的set集合
     */
    protected Set<String> getSetValueBySeparator(String value, String separator) {
        return getSetValueBySeparator(value, separator, Function.identity());
    }

    /**
     * 通过分隔符获取字符串中的set集合
     * @param value 字符串
     * @param separator 分隔符
     * @param parser 转换函数
     * @param <T> 转换后的类型
     * @return 字符串中分割的set集合
     */
    protected <T> Set<T> getSetValueBySeparator(String value, String separator, Function<String, T> parser) {
        if (!StringUtils.isEmpty(value)) {
            return Arrays.stream(value.split(separator)).map(parser).collect(Collectors.toSet());
        }
        return Collections.emptySet();
    }

    /**
     * 获取多个键值对
     * @param value 字符串形式的原内容
     * @param line 整行字符串内容
     * @param operateName 当前操作的字段名称
     * @return 多个键值对
     */
    protected Set<NameAndValue> getMultiNameAndValue(String value, String line, String operateName) {
        if (value.charAt(0) != ScriptMetadataReader.SCRIPT_START_SYMBOL
                || value.charAt(value.length() - 1) != ScriptMetadataReader.SCRIPT_END_SYMBOL) {
            throw new ScriptParseException("Illegal Script Expression " + operateName
                    + ": [" + value + "] At Expression: [" + line + "]");
        }
        Set<NameAndValue> nameAndValues = new HashSet<>();
        for (String nameAndValueStr : value.substring(1, value.length() - 1).split(KEY_VALUE_SEPARATOR)) {
            String[] splits = nameAndValueStr.split(String.valueOf(SEPARATOR));
            if (splits.length != 2) {
                throw new ScriptParseException("Illegal Script Expression " + operateName
                        + ": [" + value + "] At Expression: [" + line + "]");
            }
            nameAndValues.add(new NameAndValue(splits[0], splits[1]));
        }
        return nameAndValues;
    }
}
